#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#include <iostream>
#include "zlib.h"
#include "zconf.h"

#include "ptypes.h"
#include "rabinfp.h"

extern "C" {
#include "drecodec.h"
};
#include "packeteer.h"

#include <boost/program_options.hpp>

namespace po = boost::program_options;

typedef struct {
  unsigned char *packet;
  int length;
  ReEncodingStatus status;
} EncodedPacket;

static unsigned long storage = 0;
static unsigned long myrandom() {
  storage = 71234551*storage + 41234533;
  return storage;
}

static void seedmyrandom(unsigned long seed) {
  storage = seed;
  myrandom();
  myrandom();
}

double GetMilliSeconds() {
  struct timespec tv;
  clock_gettime(CLOCK_REALTIME, &tv);

  return (double)tv.tv_sec * 1000. + (double)tv.tv_nsec/1000000.0f;
}

int main(int argc, char **argv)
{
  int compressionLevel;
  int iterations;
  int rand_size;
  FILE *fout;
  gzFile fin;
  std::string in = "", dbName = "compressor.db", out = "";
  po::options_description desc("Allowed options");
  desc.add_options()
    ("help", "produce help message")
    ("compression,c", po::value<int>(&compressionLevel)->default_value(0), "set compression level")
    ("iterations,i", po::value<int>(&iterations)->default_value(1), "set number of iterations")
    ("random-test,r", po::value<int>(&rand_size)->default_value(0), "test with random buffer of this size")
    ("input-file,f", po::value<std::string>(), "input file")
    ("output-file,o", po::value<std::string>(), "output file")
    ("db-name,d", po::value<std::string>(), "database name")
  ;
  po::variables_map vm;
  po::store(po::parse_command_line(argc, argv, desc), vm);
  po::notify(vm);

  if (vm.count("help")) {
    std::cout << desc << "\n";
    return 1;
  }

  std::cout << "Compression level: " << compressionLevel << "\n";
  std::cout << "Iterations: " << iterations << "\n";

  if (vm.count("db-name")) {
    dbName = vm["db-name"].as<std::string>();
    std::cout << "db name " << dbName << "\n";
  }

  if (vm.count("output-file")) {
    out = vm["output-file"].as<std::string>();
    std::cout << "output file " << out << "\n";
  }

  if (vm.count("input-file")) {
    in = vm["input-file"].as<std::string>();
    std::cout << "input file " << in << "\n";
  }
  else if (!rand_size) {
    std::cout << "Need input file parameter\n";
    return 1;
  }

  if (!rand_size) {
    fin = gzopen(in.c_str(), "rb");
    if (NULL == fin) {
      std::cout << "Cannot open input file " << in.c_str() << "\n";
      return 2;
    }
  }

  if (out != "") {
    fout = fopen(out.c_str(), "wb");
    if (NULL == fout) {
        std::cout << "Cannot open output file " << out.c_str() << "\n";
        return 2;
    }
  }
  else
    fout = NULL;

  long size1;
  if (!rand_size) {
    struct stat s;
    assert(stat(in.c_str(), &s) >= 0);
    size1 = s.st_size;
  }
  else
    size1 = rand_size;

  long fsize1=size1;

 
  long bff[10000/sizeof(long)]; // aligned
  unsigned char *buffer1 = (unsigned char *)bff;
 
  unsigned char encoded[10000];
  unsigned char decoded[10000];
  int ensize, desize;

  long offset1=0;
 

  z_stream m_z;
  unsigned char z_buffer[10000]; // enough

  m_z.zalloc = Z_NULL;
  m_z.zfree = Z_NULL;
  m_z.opaque = Z_NULL;
  m_z.next_out = z_buffer;
  if(compressionLevel < 0)
    compressionLevel = 0;
  if(compressionLevel > 9)
    compressionLevel = 9;
  if(compressionLevel != 0) {
    deflateInit(&m_z, compressionLevel);
  }
    
  long zcompressed=0;
  long raw=0;
  long compressed=0;
  long packetcount = 0;
  long compressedpacketcount = 0;
  EncodedPacket packet;

  seedmyrandom(random());

  void *engine1 = InitReEngine(0, dbName.c_str());
  assert(engine1);
    
  double starttime=GetMilliSeconds();

    for (int iter = 0; iter < iterations; iter++) { 
    srandom(1);
    //Log("\nIteration %d\n", iter);
    if (0 == rand_size)
      gzseek(fin, 0, SEEK_SET); 
    if (fout)
      fseek(fout, 0, SEEK_SET); 
    long thisgo = 0;
    for(int i = 0;;i++) 
      {
	int size1 = 1460; // something random in size....

	ReEncodingStatus status1, status2;
	
	int rread1;
	if (0 == rand_size)
	    rread1 = gzread(fin, buffer1, size1); 
	else {
	    int nr;
	    long *pl = bff;
	    for (nr = 0; nr < (size1 + sizeof(long) + 1)/sizeof(long); nr++, pl++) {
		*pl = random();
	    }
		int d = myrandom() % 3;
		for (int dd = 0; dd < d; dd++) {
		    int dstart = myrandom() % size1;
		    if (dstart == size1)
			continue;
		    int dend = (dstart + myrandom()) % (size1-dstart);
		    for (int ddd = dstart; ddd < dend; ddd += sizeof(long))
			bff[ddd/sizeof(long)] = myrandom();
		}
	    rread1 = size1;
	}
	//Log("Left packet %d length %d\n", i+1, rread1);

	if(rread1 > 0) {
	  raw += rread1;
	  thisgo += rread1;

	  packetcount++;
          if ((packetcount % 10000) == 0)
	    fprintf (stderr, "Processed %ld packets\n", packetcount);

	  // encode it
	  //Log("\nEncoded left ");
	  ReStatus v;
          unsigned short l;
	  int r = ReEncode(engine1, buffer1, rread1, encoded, 10000, &v, &l, 1);
	  status1 = v.encoding;
	  ensize = (int)l;
	  //printf("dre_encode returned ret %d outsize %d\n", status1, ensize);
	  //Log("   result: %d -> %d, status %d\n", rread1, ensize, status1);

	  packet.status=status1;

	  switch(status1) {
	  case ReECompressedStored:
	  case ReECompressedNotStored:
	    packet.length = ensize;
	    packet.packet = encoded;
	    compressedpacketcount++;
	    break;
	  case ReENotCompressedStored:
	    packet.length = rread1;
	    packet.packet = buffer1;
	    break;
	  }
	  compressed += packet.length;
	  if(compressionLevel > 0) {
	    m_z.next_in = packet.packet;
	    m_z.avail_in = rread1;

	    m_z.next_out = z_buffer;
	    m_z.total_out = 0;
	    m_z.avail_out = sizeof(z_buffer);
	    	   
	    int ret = deflate(&m_z, 0);
	    assert(ret != Z_STREAM_ERROR);

	    zcompressed += sizeof(z_buffer) - m_z.avail_out;

	    if (fout)
	      size_t r = fwrite(z_buffer, 1, sizeof(z_buffer) - m_z.avail_out, fout);
	    
	  } else {
	    if (fout)
	      size_t r = fwrite(packet.packet, 1, packet.length, fout);
	  }
	}

	if((rread1 == 0) || (rand_size != 0 && thisgo >= fsize1))
	  break;
      }
    }

  double endtime=GetMilliSeconds();

  if(compressionLevel > 0)
    deflateEnd(&m_z);

  if (!rand_size)
    gzclose(fin);
  if (fout)
    fclose(fout);

  struct ReEngineStats stats;
  ReGetStats(engine1, &stats);

  ShutdownReEngine(engine1);

  printf("Completed %d iterations, processed %ld packets, %ld bytes\n", iterations, packetcount, (long)fsize1 * (long)iterations);
  printf("Elapsed milliseconds: %f\n", endtime-starttime);
  printf("Throughput: %5.2lf Mbps\r\n", (fsize1*iterations)*8000./1024./1024./(endtime-starttime));
  printf("Raw bytes: %ld, compressed packets: %ld, bytes: %ld\n", raw, compressedpacketcount, compressed);
  printf("Compressed to: %4.2f%%\r\n", 100.f*(float)compressed/(float)raw);

  if(compressionLevel > 0) {
    printf("With zlib %d compressed to: %4.2f%%\r\n", compressionLevel, 100.f*(float)zcompressed/(float)raw);
  }

  printf("ReEngine stats engine1:\n");
  printf("\tCache stats: purges: %ld, gets: %ld, saves: %ld\n", 
	stats.disk_cache_purges,
	stats.disk_cache_gets,
	stats.disk_cache_saves);
  printf("\tIndex stats: puts: %ld, deletes: %ld, found: %ld, notfound: %ld\n", 
	stats.index_puts,
	stats.index_deletes,
	stats.index_found,
	stats.index_notfound);

  return 0;
}
